<!-- eslint-disable indent -->
<template>
  <div
    class="question-input"
    :style="{
      width: `${questionInputWith}px`,
      bottom: webIMSource === 'workbench' ? '0' : `${questionInputBottom}px`,
    }"
    v-height-watch="onHeightChange"
  >
    <!-- 停止生成 -->
    <!-- <div class="stop-button" v-if="isGeneratingReply">
          <v-button icon="basic_stop_line" remote @click="onStopStream">停止生成</v-button>
      </div> -->
    <RecommendQuestionList
      v-if="showRecommendList"
      type="input"
      @itemClick="onSendRecommendQuestion"
    />

    <div v-if="disableInput[currentVisitorId]" class="question-mask"></div>
    <div
      :class="[
        'question-input-inner',
        { inputFocus: !isInputDisabled && (inputFocus || !isSendDisabled) },
        { disabled: isInputDisabled },
      ]"
    >
      <div class="question-input-inner__image-outer" v-if="docUrls.length > 0">
        <div
          v-for="(item, index) in docUrls"
          :key="index"
          class="exist-doc"
          @click="previewDoc(index)"
        >
          <div class="doc-icon-cont">
            <v-icon :name="getFileIcon(item.filetype)" remote class="doc-icon">
            </v-icon>
          </div>
          <div class="doc-txt-cont">
            <div class="delete-button">
              <v-button
                icon="basic_error_fill"
                remote
                @click="(e) => handleDeleteDoc(e, index)"
              ></v-button>
            </div>
            <div class="filename" :title="`${item.filename}.${item.filetype}`">
              {{ fileString(item.filename, 6, item.filetype) }}
            </div>
            <div
              :class="`doc-txt-info ${
                item.resolveProcess === -1 ? 'red-txt' : ''
              }`"
              :title="
                item.resolveProcess === -1
                  ? `${item.error_message}`
                  : item.process === -1
                  ? `解析中·${item.resolveProcess || 0}%`
                  : item.process === 1
                  ? formateSize(item.filesize)
                  : `上传中·${Math.floor(
                      (item.process > 0 ? item.process : 0) * 100
                    )}%`
              "
            >
              {{
                item.resolveProcess === -1
                  ? `${item.error_message}`
                  : item.process === -1
                  ? `解析中·${item.resolveProcess || 0}%`
                  : item.process === 1
                  ? formateSize(item.filesize)
                  : `上传中·${Math.floor(
                      (item.process > 0 ? item.process : 0) * 100
                    )}%`
              }}
              <v-icon
                v-if="item.resolveProcess === -1"
                name="basic_refresh_line"
                class="refresh-icon"
                remote
                @click="(e) => handleDocParse(item, index, e)"
              ></v-icon>
            </div>
          </div>
        </div>
      </div>
      <!-- <v-textarea
              v-if="isExperience"
              @focus="updateFocus(true)"
              @blur="updateFocus(false)"
              :readonly="disableInput[currentVisitorId] || isGeneratingReply || isThinking || isDisabled"
              v-model="question"
              @keydown.native="onPressEnter($event)"
              :placeholder="disableInput[currentVisitorId] ? '已结束会话' : placeholdertext"
              :placeholder="disableInput[currentVisitorId] ? '已结束会话' : shouldShowPicUpload && !isDeepSeekApp ? '请输入你的问题，支持上传图片或文件。' : shouldShowPicUpload ? '请输入你的问题，支持上传文件。' : '请输入你的问题。'"
              :autofocus="webIMSource === 'manage' ? false : true"
              :autoheight="true"
              class="question-input-inner__textarea"
          ></v-textarea> -->

      <QaEditor
        ref="htmlEditorRef"
        :parseLimit="realWordLimit"
        @keydown.native="onPressEnter($event)"
        :placeholder="placeholderText"
        :autofocus="webIMSource === 'manage' ? false : true"
        :autoheight="true"
        class="question-input-inner__textarea"
        :hideToolBar="true"
        :key="editorKey"
        :value="question"
        :readOnly="isInputDisabled"
        :disabled="isInputDisabled"
        :acceptImgType="acceptImgType"
        @input="editorValChange"
        @focus="updateFocus(true)"
        @blur="updateFocus(false)"
      />
      <div class="question-input-inner__toolbar">
        <div class="toolbar-left">
          <label
            for="file-upload"
            class="upload-label"
            v-if="shouldShowPicUpload && !isDeepSeekApp"
          >
            <input
              type="file"
              ref="fileInput"
              id="file-upload"
              :accept="acceptImgType"
              class="image-input"
              :disabled="
                disableInput[currentVisitorId] ||
                isGeneratingReply ||
                isThinking ||
                isDisabled
              "
              @change="handleFileUpload"
            />
            <v-icon
              :class="
                disableInput[currentVisitorId] ||
                isGeneratingReply ||
                isThinking ||
                isDisabled
                  ? 'v-icon disabled'
                  : 'v-icon'
              "
              name="basic_picture_line"
              size="20"
              remote
              v-tip:picMsg.hover
            ></v-icon>
          </label>
          <label
            for="file-upload-doc"
            class="upload-label"
            v-if="shouldShowPicUpload"
          >
            <input
              type="file"
              ref="fileInputDoc"
              id="file-upload-doc"
              :accept="acceptDocType"
              class="image-input"
              :disabled="
                disableInput[currentVisitorId] ||
                isGeneratingReply ||
                isThinking ||
                isDisabled ||
                deepSeekUpload
              "
              @change="handleDocUpload"
            />
            <v-icon
              :class="
                disableInput[currentVisitorId] ||
                isGeneratingReply ||
                isThinking ||
                isDisabled
                  ? 'v-icon disabled'
                  : 'v-icon'
              "
              name="basic_file_line"
              size="20"
              remote
              v-tip:docMsg.hover
            ></v-icon>
          </label>
          <asr-animation
            v-if="enableVoiceInteract"
            :class="[
              'upload-label',
              isASRActive ? 'asr-loading' : '',
              isAsrDisabled ? 'v-icon disabled' : 'v-icon',
            ]"
            :is-loading="isASRActive"
            :is-disabled="isAsrDisabled"
            @click="onAsrAnimationClick"
          />

          <v-tip
            ref="picMsg"
            :disabled="isInputDisabled"
            size="small"
            placement="right"
            >上传图片。支持{{
              acceptImgType
            }}格式，单个图片最大不超过20MB</v-tip
          >
          <v-tip
            ref="docMsg"
            :disabled="isInputDisabled"
            size="small"
            placement="right"
            >上传文档。支持{{
              acceptDocType
            }}格式，单个文档最大15MB，不超过1000页</v-tip
          >

          <!-- 联网搜索 icon -->
          <!-- v-if="this.searchNetwork" -->
          <div
            :class="`toolbar-left-search-icon ${getSearchIconClass}`"
            @click="handleClickSearchNetwork"
          >
            <v-icon
              v-if="!setSearchLoading"
              remote
              name="basic_url_line"
            ></v-icon>
            <v-spinner v-else status="default"></v-spinner>
            联网搜索
          </div>
          <!-- <v-icon :class="(disableInput[currentVisitorId] || isGeneratingReply || isThinking) ? 'v-icon disabled' : 'v-icon'" name="basic_emoji_line" size="20" remote></v-icon>

                  <v-icon :class="(disableInput[currentVisitorId] || isGeneratingReply || isThinking) ? 'v-icon disabled' : 'v-icon'" name="basic_file_line" size="20" remote></v-icon> -->
        </div>
        <div class="toolbar-right">
          <div class="toolbar-info">
            <span :class="wordCount > realWordLimit ? 'red-txt' : ''">{{
              wordCount
            }}</span
            >/{{ realWordLimit }}
          </div>
          <div
            :class="['stop-icon', { disabled: disableInput[currentVisitorId] }]"
            v-if="webIMSource === 'workbench'"
          >
            <v-icon
              name="basic_exit_line"
              @click="onStopChat"
              v-tip:closeSession.hover
              remote
            ></v-icon>
            <v-tip ref="closeSession" size="small" placement="top"
              >结束会话</v-tip
            >
          </div>
          <div class="split-line" v-if="webIMSource === 'workbench'"></div>
          <!-- stop 停止生成 icon -->
          <div v-if="isGeneratingReply" :class="['send-icon']">
            <v-icon
              name="basic_stop_fill"
              size="28"
              @click="onStopStream"
              v-tip:stopSendMsg.hover
              remote
            ></v-icon>
            <v-tip ref="stopSendMsg" size="small" placement="top"
              >停止生成</v-tip
            >
          </div>
          <!-- send 发送 icon -->
          <div
            v-else
            :class="[
              'send-icon',
              { disabled: isSendDisabled || disableInput[currentVisitorId] },
            ]"
          >
            <v-icon
              name="basic_send_fill"
              size="28"
              @click="onSendQuestion"
              v-tip:sendMsg.hover
              remote
            ></v-icon>
            <v-tip ref="sendMsg" size="small" placement="top">发送</v-tip>
          </div>
        </div>
      </div>
    </div>
    <div v-if="webIMSource === 'manage'" class="tips">
      <div>当前为测试体验环境，发布后点击体验链接即可体验生产环境效果。</div>
      <div v-if="isTrial">免费额度：{{ usedToken }}/{{ freeToken }} Tokens</div>
    </div>
  </div>
</template>

<script>
import QaEditor from "@/components/QaEditor/index.vue";
import asrAnimation from "./asr-animation.vue";
import elementResizeDetectorMaker from "element-resize-detector";
import Cookies from "js-cookie";
import {
  getCookie,
  getQueryVariable,
  scrollToBottom,
  escapeUserInput,
  getUserCategory,
  getUUID,
  cos,
  checkImageResolution,
  getFileIcon,
  formateSize,
  fileString,
  html2Markdown,
  generateRequestId,
  getShareCode,
  wordCounter,
} from "@/utils";
import {
  MESSAGE_TYPE,
  WORD_LIMIT,
  APP_TYPE,
  COS_BIZ_TYPE,
  LIMIT_APPID,
  DOC_UPLOAD_LIMIT,
} from "@/constants";

import RecommendQuestionList from "@/pages/chat/components/recommend-question-list.vue";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import moment from "moment";

export default {
  name: "QuestionInput",
  components: {
    QaEditor,
    RecommendQuestionList,
    asrAnimation,
  },
  props: {
    currentVisitorId: {
      msgData: {
        type: Number,
        default: 0,
      },
    },
    // 目前只针对 标签应用和 摘要应用 进行动态获取
    wordLimit: {
      type: Number,
      default: WORD_LIMIT,
    },
    isDisabled: {
      type: Boolean,
      default: false,
    },
    isExperience: {
      type: Boolean,
      default: false,
    },
    hasDynamicWordLimit: {
      type: Boolean,
      default: false,
    },
    isASRActive: {
      type: Boolean,
      default: false,
    },
    enableVoiceInteract: {
      type: Boolean,
      default: false,
    },
    isReading: {
      type: Boolean,
      default: false,
    },
  },
  computed: {
    getSearchIconClass() {
      switch (this.searchNetwork) {
        case "enable":
          return "is-active";
        case "disable":
          return "is-not-active";
        case "":
        default:
          return "display-none";
      }
    },
    placeholderText() {
      const webimToken = window.webimToken[0] || {};
      let isTokenEnough = !(webimToken.balance && webimToken.balance > 0);
      const isAgentPattern =
        webimToken.pattern && webimToken.pattern === "agent";
      if (isAgentPattern) {
        isTokenEnough = false;
      }
      if (this.disableInput[this.currentVisitorId]) {
        return "已结束会话";
      } else if (isTokenEnough) {
        return "账户欠费或资源包耗尽，请购买后重试";
      } else {
        // 如果是异步调用，仅仅展示 ‘发送任意消息，触发“异步调试任务”’
        if (webimToken.asy && webimToken.debug_mode === 1) {
          return "发送任意消息，触发“异步调试任务";
        }
        if (this.shouldShowPicUpload && !this.isDeepSeekApp) {
          return "请输入你的问题，支持上传图片或文件。";
        } else {
          if (this.shouldShowPicUpload) {
            return "请输入你的问题，支持上传文件。";
          } else {
            return "请输入你的问题。";
          }
        }
      }
      // "disableInput[currentVisitorId] ? '已结束会话' : shouldShowPicUpload && !isDeepSeekApp ? '请输入你的问题，支持上传图片或文件。' : shouldShowPicUpload ? '请输入你的问题，支持上传文件。' : '请输入你的问题。'"
    },

    isInputDisabled() {
      console.log(
        "readonly-------disableinput--->",
        this.disableInput[this.currentVisitorId],
        "-",
        this.isGeneratingReply,
        "-",
        this.isThinking,
        "-",
        this.isDisabled
      );
      return (
        this.disableInput[this.currentVisitorId] ||
        this.isGeneratingReply ||
        this.isThinking ||
        this.isDisabled
      );
    },
    wordCount() {
      return wordCounter(this.question);
    },
    isSendDisabled() {
      console.log(
        !this.question.trim() && this.docUrls.length === 0,
        this.isPicUploading,
        this.docUrls.findIndex((i) => !i.process || i.process < 1) > -1
      );
      return (
        (!this.question.trim() && this.docUrls.length === 0) ||
        this.isPicUploading ||
        this.docUrls.findIndex((i) => !i.process || i.process < 1) > -1
      );
    },
    showRecommendList() {
      return (
        this.msgList.filter((msg) => msg.type !== 6).length > 0 &&
        !(
          this.disableInput[this.currentVisitorId] ||
          this.isGeneratingReply ||
          this.isThinking
        )
      );
    },
    isDeepSeekApp() {
      // deepseek官方app不展示图片 暂时这么处理
      let webimToken = window.webimToken[0] || {};
      // console.log('isDeepSeekApp webimToken--------->', process.env.SERVER_ENV, '=======', webimToken.bot_biz_id, '=======', webimToken);
      if (
        process.env.SERVER_ENV === "prod" ||
        process.env.SERVER_ENV === "local"
      ) {
        return webimToken.bot_biz_id === "1887453086201675776";
      } else if (
        process.env.SERVER_ENV === "test" ||
        process.env.SERVER_ENV === "local"
      ) {
        return webimToken.bot_biz_id === "1887055767721213952";
      } else {
        return false;
      }
    },
    deepSeekUpload() {
      if (this.isDeepSeekApp) {
        return this.docUrls.length > 0;
      } else {
        return false;
      }
    },
    isAgentModel() {
      const webimToken = window.webimToken[0];
      const patternConfig = webimToken.pattern;
      return patternConfig === "agent";
    },
    shouldShowPicUpload() {
      let webimToken = window.webimToken[0];
      console.log("shouldShowPicUpload webimToken--------->", webimToken);
      const patternConfig = webimToken.pattern;
      // const patternConfig = getQueryVariable('pattern');
      console.log(
        "chat shouldShowPicUpload--------》patternConfig .....",
        patternConfig
      );
      // 单工作流模式无法上传图片和文件
      if (patternConfig === "single_workflow") {
        return false;
      }
      if (webimToken.is_agent_model) {
        return false;
      }
      // 当前只在知识库问答（APP_TYPE.QA）显示, 本期暂时隐藏体验中心的图片对话功能 !getQueryVariable('experience') &&
      return (
        ["manage", "client"].includes(this.webIMSource) &&
        (getQueryVariable("app_type") ||
          window.localStorage.getItem("app_type")) === APP_TYPE.QA
      );
    },
    isTrial() {
      return getQueryVariable("is_trial") === "true";
    },
    isAsrDisabled() {
      return (
        this.disableInput[this.currentVisitorId] ||
        this.isGeneratingReply ||
        this.isThinking ||
        this.isDisabled
      );
    },
  },
  data() {
    return {
      setSearchLoading: false,
      searchNetwork: "", // 标识联网搜索icon
      curSelection: null,
      editorKey: 0,
      answer: "",
      questionInputWith: 360,
      questionInputBottom: 0,
      question: "",
      docUrls: [],
      isPicUploading: false,
      inputFocus: false,
      disableInput: {},
      isGeneratingReply: false, // 是否生成回答中
      isThinking: false, // 是否思考中
      msgList: [],
      sseUrl: "",
      ROLE_SSE_PATH: "/v1/qbot/chat/docParse",
      systemError: [
        "disconnect", // 连接异常中断
        "connectError", // 连接错误
        "SocketError", // 连接请求错误
        "reconnectError", // 重新连接错误
        "reconnectFailed", // 重新连接失败
      ],
      usedToken: "-",
      freeToken: "-",
      acceptDocType: ".doc,.docx,.ppt,.pptx,.pdf,.txt,.xls,.xlsx",
      acceptImgType: ".jpg,.png,.jpeg,.bmp",
      realWordLimit: 12000,
      fakeProcessTimer: {},
    };
  },
  created() {
    if (this.webIMSource === "workbench") {
      // 监听复制到输入框消息
      this.$eventHub.$on("answerCopySearchMsg", (res) => {
        this.question = res; // todo-elaine 待验证
      });
      this.$eventHub.$on("currentVisitorChange", () => {
        this.question = "";
      });
      // 监听转人工变化消息
      this.$eventHub.$on("transferInfoChange", (res) => {
        // 当前的visitorid?
        console.log("transferInfoChange", res);
        if (
          this.currentVisitorId &&
          res[this.currentVisitorId] !== undefined &&
          !res[this.currentVisitorId]
        ) {
          this.disableInput = {
            ...this.disableInput,
            [this.currentVisitorId]: true,
          };
          this.question = "";
        } else {
          this.disableInput = {
            ...this.disableInput,
            [this.currentVisitorId]: false,
          };
        }
      });
    }
    if (this.webIMSource === "manage") {
      this.$eventHub.$on("token_state_change", (data) => {
        const { used_count = "-", free_count = "-" } = data || {};
        this.usedToken = used_count;
        this.freeToken = free_count;
      });
    }
    // 如果ws链接出错, 恢复输入框可输入状态
    this.$eventHub.$on("systemEvents", (args) => {
      if (!this.finishWs && this.systemError.indexOf(args.errorName) > -1) {
        this.isGeneratingReply = false;
        this.isThinking = false;
      }
    });

    // 报错时候，将停止生成和正在思考复位
    this.$eventHub.$on("client_systemEvents", (res) => {
      console.log("vvvvvvvvvvvvvvvvvv client_systemEvents", res);
      if (!res || !res.error) return;
      this.isGeneratingReply = false;
      this.isThinking = false;
    });

    // 监听答案消息队列变更事件, 判断是否正在思考/正在生成答案
    this.$eventHub.$on("client_msgContentChange", (res) => {
      // console.log('vvvvvvvvvvvvvvvvvv ====config==== client_msgContentChange', res);

      const { chatsContent, type } = res;
      this.msgList = chatsContent;
      // if (type !== MESSAGE_TYPE.ANSWER) return;
      if (
        type !== MESSAGE_TYPE.ANSWER &&
        type !== MESSAGE_TYPE.STOP &&
        type !== MESSAGE_TYPE.HISTORY
      )
        return;
      // this.isGeneratingReply = !!chatsContent.find(r => r.is_final === false && !r.is_connection_error);
      this.isGeneratingReply = !!chatsContent.find((r) => {
        if (r.loading_message) {
          return (
            r.agent_thought && r.is_final === false && !r.is_connection_error
          );
        } else {
          return r.is_final === false && !r.is_connection_error;
        }
      });

      this.isThinking =
        chatsContent.length > 0 &&
        res.chatsContent[res.chatsContent.length - 1].loading_message;
    });
  },
  mounted() {
    const webimToken = window.webimToken[0] || {};
    this.searchNetwork = webimToken.search_network || "";
    console.log("SSE_BASE_URL", process.env.SSE_BASE_URL);
    this.sseUrl = `https://wss.lke.cloud.tencent.com/v1/qbot/chat/docParse`;
    const erd = elementResizeDetectorMaker();
    const questionInputParentDom =
      document.querySelector(".question-input").parentElement;

    // 输入框宽度
    erd.listenTo(questionInputParentDom, (element) => {
      this.questionInputWith = element.clientWidth;
    });

    // 输入框bootom间距（坐席端置底无需处理，用户端需计算）
    if (this.webIMSource === "client") {
      const bodyDom = document.body;
      const chatWrapperDom = document.querySelector(".chat-wrap__main");
      const footerDom = document.querySelector(".client-side-wrap__footer");

      erd.listenTo(bodyDom, () => {
        if (this.isMobile) {
          this.questionInputBottom =
            bodyDom.clientHeight > chatWrapperDom.clientHeight
              ? (bodyDom.clientHeight - chatWrapperDom.clientHeight) / 2
              : 0;
        } else {
          this.questionInputBottom =
            bodyDom.clientHeight > chatWrapperDom.clientHeight
              ? footerDom.clientHeight /* 底部免责声明高度；原来是body高度减去对话框高度，因为上下没有其他元素 */
              : 0;
        }
      });
    }
  },
  directives: {
    heightWatch: {
      bind(el, binding, vnode) {
        const onHeightChange = binding.value;
        const observer = new ResizeObserver((entries) => {
          for (const entry of entries) {
            const height = entry.contentRect.height;
            onHeightChange(height, vnode.context);
          }
        });
        observer.observe(el);
        el.__resizeObserver__ = observer;
      },
      unbind(el) {
        if (el.__resizeObserver__) {
          el.__resizeObserver__.disconnect();
          delete el.__resizeObserver__;
        }
      },
    },
  },
  methods: {
    getFileIcon,
    formateSize,
    fileString,
    async handleClickSearchNetwork() {
      this.setSearchLoading = true;
      try {
        const saveVal = this.searchNetwork === "enable" ? "disable" : "enable";
        this.searchNetwork = saveVal;
        window.webimToken[0].search_network = saveVal;
      } catch (err) {
        console.error("ModifyUserDialogConfig ERROR", err);
      } finally {
        this.setSearchLoading = false;
      }
    },
    // onHeightChange (height, context) {
    //     console.log('Height changed to', height);
    //     console.log('Context is', context);
    //     if (document.querySelector('.qa-item:last-child')) {
    //         document.querySelector('.qa-item:last-child').style.paddingBottom = `${height}px`;
    //     }
    // },
    onHeightChange(height, context) {
      console.log("Height changed to", height);
      console.log("Context is", context);
      if (document.querySelector(".qa-item-last")) {
        const root = document.documentElement;
        // 更改 CSS 变量的值
        root.style.setProperty("--self-padding-bottom", `${height}px`);
      }
    },
    editorValChange(val) {
      console.log(val, arguments);
      this.question = val;
      this.checkAndUpdateWordLimit();
    },
    previewImg(index) {
      console.log("previewImg index", index);
      // this.$emit('previewImg', index, this.picUrls);
    },
    previewDoc(index) {
      if (this.isAgentModel) {
        cos
          .getDocHtmlUrl(COS_BIZ_TYPE.IMG_UPLOAD, this.docUrls[index])
          .then((res) => {
            window.open(res);
          })
          .catch((err) => {
            this.$message.error(JSON.stringify(err) || "获取预览地址失败");
          });
        return;
      }
      if (this.docUrls[index].doc_id && this.docUrls[index].doc_id !== "0") {
        const sharecode = getShareCode();
        const botBizId =
          window.localStorage.getItem(`bot_biz_id_im_${sharecode}`) ||
          getQueryVariable("bot_biz_id");
        const tdeId = Cookies.get("_tde_id") || "";
        const cat = getUserCategory();
        tdeId
          ? window.open(
              `https://tde.xiaowei.cloud.tencent.com/preview?id=${this.docUrls[index].doc_id}&botBizId=${botBizId}&typeKey=realtime&cat=${cat}`
            )
          : window.open(
              `https://lke.cloud.tencent.com/preview?id=${this.docUrls[index].doc_id}&botBizId=${botBizId}&typeKey=realtime&cat=${cat}`
            );
      } else if (this.docUrls[index].error_message) {
        this.$message.error(this.docUrls[index].error_message);
      }
    },
    checkAndUpdateWordLimit() {
      // 特殊逻辑，多模态仅支持8K token,所以需要在图片的情况下限制token数
      if (this.wordLimit > 6000) {
        // this.question 包含 markdown格式的图片时
        const imgUrls = [];
        const replaceResult = this.question.replace(/<img[^>]*>/g, (match) => {
          imgUrls.push(match);
          return "IMG_URL_PREFIX";
        });
        console.log(replaceResult);
        if (imgUrls.length > 0) {
          this.realWordLimit = 6000;
        } else {
          this.realWordLimit = this.wordLimit;
        }
      }
    },
    updateFocus(isFocus) {
      this.inputFocus = isFocus;
      this.checkAndUpdateWordLimit();
    },
    // 回车键
    onPressEnter(event) {
      if (event.keyCode === 13) {
        if (event.metaKey || event.ctrlKey) {
          this.$refs.htmlEditorRef.editor.dangerouslyInsertHtml("<br/>");
        } else {
          event.preventDefault();
          this.onSendQuestion();
        }
      }
    },
    // 删除文档
    handleDeleteDoc(event, index) {
      event.stopPropagation();
      if (
        this.docUrls[index] &&
        this.fakeProcessTimer[this.docUrls[index].data.name]
      ) {
        clearTimeout(this.fakeProcessTimer[this.docUrls[index].data.name]);
        this.fakeProcessTimer[this.docUrls[index].data.name] = null;
      }
      this.docUrls[index].controller && this.docUrls[index].controller.abort();
      const tempArr = [...this.docUrls];
      tempArr.splice(index, 1);
      this.docUrls = tempArr;
    },
    timeOutFun(findFileIndex, fileName) {
      console.log(
        moment().format("HH:mm:ss"),
        this.fakeProcessTimer[fileName],
        findFileIndex,
        this.docUrls,
        this.docUrls[findFileIndex].resolveProcess
      );
      if (
        this.fakeProcessTimer[fileName] &&
        this.docUrls[findFileIndex].resolveProcess < 98
      ) {
        const temp = this.docUrls[findFileIndex].resolveProcess + 1;
        this.$set(this.docUrls, findFileIndex, {
          ...this.docUrls[findFileIndex],
          resolveProcess: temp,
        });
        this.fakeProcessTimer[fileName] = setTimeout(() => {
          this.timeOutFun(findFileIndex, fileName);
        }, 1000);
        console.log("this.docUrls[findFileIndex].resolveProcess + 1", temp);
      } else {
        console.log("clearTimeout");
        clearTimeout(this.fakeProcessTimer[fileName]);
      }
    },

    async handleAgentDoc(file, findFileIndex, event) {
      const downloadUrl = await cos.getDownloadUrl(
        COS_BIZ_TYPE.IMG_UPLOAD,
        file,
        () => {
          console.error("cos.getDownloadUrl error");
        },
        24 * 3600
      );
      if (
        this.docUrls[findFileIndex] &&
        this.docUrls[findFileIndex].data.name === file.data.name
      ) {
        this.$set(this.docUrls, findFileIndex, {
          ...this.docUrls[findFileIndex],
          ...file,
          file_url: downloadUrl,
          doc_id: "",
          resolveProcess: 100,
        });
      }
    },

    // 文档解析
    async handleDocParse(file, findFileIndex, event) {
      event && event.stopPropagation();
      const controller = new AbortController();
      this.$set(this.docUrls, findFileIndex, {
        ...this.docUrls[findFileIndex],
        ...file,
        process: -1,
        controller,
      });
      const request_id = generateRequestId();
      const fileType =
        file.data.name.split(".")[file.data.name.split(".").length - 1];
      const session_id = this.$clientData.getConfigInfo().session_id;
      const data = {
        session_id: session_id,
        request_id: request_id,
        cos_bucket: file.cos_bucket,
        file_type: fileType,
        file_name: file.data.name,
        cos_url: file.cos_url,
        e_tag: file.e_tag,
        cos_hash: file.cos_hash,
        size: `${file.data.size}`,
      };
      if (window.webimToken[0].app_key) {
        data.bot_app_key = window.webimToken[0].app_key;
      }
      const self = this;
      fetchEventSource(this.sseUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        signal: controller.signal,
        body: JSON.stringify(data),
        openWhenHidden: true,
        onmessage(eventStreamData) {
          const resData = eventStreamData.data;
          console.log(moment().format("HH:mm:ss"), "==>", resData);
          if (!resData) return; // 空字符
          if (self.fakeProcessTimer[file.data.name]) {
            clearTimeout(self.fakeProcessTimer[file.data.name]);
            self.fakeProcessTimer[file.data.name] = null;
          }
          try {
            const resObj = JSON.parse(resData);
            console.log("== 解析成功 ==", resObj);
            if (
              self.docUrls[findFileIndex] &&
              self.docUrls[findFileIndex].data.name === file.data.name
            ) {
              if (
                resObj.type === "error" ||
                resObj.payload.status === "FAILED"
              ) {
                resObj.payload.error_message
                  ? self.$message.error(resObj.payload.error_message)
                  : self.$message.error(`${resObj.payload.error_message}`);
                self.$set(self.docUrls, findFileIndex, {
                  ...self.docUrls[findFileIndex],
                  ...file,
                  doc_id: resObj.payload.doc_id,
                  resolveProcess: -1,
                  error_message: resObj.payload.error_message,
                });
              } else {
                if (resObj.payload.process === 100) {
                  self.$set(self.docUrls, findFileIndex, {
                    ...self.docUrls[findFileIndex],
                    ...file,
                    doc_id: resObj.payload.doc_id,
                    resolveProcess: resObj.payload.process,
                  });
                } else {
                  if (
                    !self.docUrls[findFileIndex].resolveProcess ||
                    self.docUrls[findFileIndex].resolveProcess <
                      resObj.payload.process
                  ) {
                    self.$set(self.docUrls, findFileIndex, {
                      ...self.docUrls[findFileIndex],
                      ...file,
                      doc_id: resObj.payload.doc_id,
                      process: -1,
                      resolveProcess: resObj.payload.process,
                    });
                  } else {
                    // 使用 setTimeout 增加虚假的进度条，每隔1000ms进度加1
                    self.fakeProcessTimer[file.data.name] = setTimeout(
                      () => self.timeOutFun(findFileIndex, file.data.name),
                      1000
                    );
                  }
                }
              }
            }
          } catch (e) {
            console.log("== 解析失败 ==", e);
          }
        },
        onclose() {
          console.log("== EventSource 关闭 ==");
        },
        onerror(err) {
          console.log("== EventSource 异常 ==", err);
        },
      }).then((eventSource) => {
        console.log("== EventSource 打开 ==", eventSource);
      });
    },
    checkFileType(event, acceptType) {
      const file = event.target.files[0];
      const allowedExtensions = acceptType.split(",");
      // 获取文件扩展名
      const fileExtension = "." + file.name.split(".").pop().toLowerCase();
      // 检查文件扩展名是否在允许的扩展名列表中
      if (!allowedExtensions.includes(fileExtension)) {
        this.$message.error("不允许的文件类型！");
        event.target.value = ""; // 清除文件输入值
        return false;
      }
      return true;
    },
    // 选择文档
    async handleDocUpload(event) {
      event.stopPropagation();
      const botBizId = getCookie("bot_biz_id");
      const docLimit = LIMIT_APPID.includes(botBizId) ? 50 : DOC_UPLOAD_LIMIT;
      if (this.docUrls.length >= docLimit) {
        this.$message.error(`最多上传${docLimit}个文件`);
        event.target.value = ""; // 清除文件输入值
        return;
      }
      this.isPicUploading = true;
      let files = Array.from(event.target.files).filter(
        (i) => this.docUrls.findIndex((doc) => doc.data.name === i.name) === -1
      );
      if (files.length === 0) {
        this.$message.error("请不要重复选中相同的文件。");
        event.target.value = ""; // 清除文件输入值
        return;
      }
      if (!this.checkFileType(event, this.acceptDocType)) return;
      files = files.map((file) => ({
        data: file,
        saveName: `${getUUID()}-${file.name}`,
      }));
      // 文档大小
      if (files.some((file) => file.data.size > 15 * 1024 * 1024)) {
        this.$message.error("文档大小不能超过15MB，请重新选择");
        event.target.value = ""; // 清除文件输入值
        return;
      }
      if (files.some((file) => file.data.size <= 0)) {
        this.$message.error("文档大小应大于0MB，请重新选择");
        event.target.value = ""; // 清除文件输入值
        return;
      }
      const uploadProcess = async (name, file) => {
        if (file && file.data) {
          const findFileIndex = this.docUrls.findIndex(
            (i) => i.saveName === name
          );
          console.log("this.findFileIndex", findFileIndex);
          if (findFileIndex > -1) {
            if (file.process >= 1) {
              this.handleDocParse(file, findFileIndex);
            } else {
              this.$set(this.docUrls, findFileIndex, {
                ...this.docUrls[findFileIndex],
                ...file,
              });
            }
          } else {
            const lastIndex = file.data.name.lastIndexOf(".");
            const filename = file.data.name.slice(0, lastIndex);
            const filetype = file.data.name.slice(lastIndex + 1);
            this.docUrls.push({
              ...file,
              filetype,
              filename,
              filesize: `${file.data.size}`,
            });
          }
        }
      };
      // 在这里处理文件上传逻辑
      cos
        .upload(
          false,
          COS_BIZ_TYPE.IMG_UPLOAD,
          files,
          uploadProcess,
          null,
          false
        )
        .then((result) => {
          console.log("result111", result);
          this.isPicUploading = false;
          // const url = (result && result.url) || '';
          // 通过清除引用的方式重置input的值
          this.$refs.fileInputDoc.value = "";
        });
    },
    // 选择图片
    async handleFileUpload(event) {
      const image_security = this.$clientData.getConfigInfo().image_security;
      this.isPicUploading = true;
      const files = Array.from(event.target.files).map((file) => ({
        data: file,
        saveName: `${getUUID()}-${file.name}`,
      }));
      // 显示图片大小
      if (files.some((file) => file.data.size > 20 * 1024 * 1024)) {
        this.$message.error("图片大小不能超过20MB，请重新选择");
        this.isPicUploading = false;
        return;
      }
      if (files.some((file) => file.data.size <= 0)) {
        this.$message.error("图片大小应大于0MB，请重新选择");
        this.isPicUploading = false;
        return;
      }
      if (!this.checkFileType(event, this.acceptImgType)) return;

      checkImageResolution(files[0].data)
        .then(() => {
          if (!image_security) {
            cos.upload(true, COS_BIZ_TYPE.DOC_UPLOAD, files).then((result) => {
              console.log("ispublic...........result1", result, image_security);
              this.isPicUploading = false;
              const url = (result && result.url) || "";
              // 通过清除引用的方式重置input的值
              this.$refs.fileInput.value = "";
              try {
                this.$refs.htmlEditorRef.editor.restoreSelection();
                this.$refs.htmlEditorRef.editor.dangerouslyInsertHtml(
                  `<img src="${url}" />`
                );
              } catch (error) {
                this.$refs.htmlEditorRef.lastSelection();
                this.$refs.htmlEditorRef.editor.dangerouslyInsertHtml(
                  `<img src="${url}" />`
                );
              }
            });
          } else {
            // 在这里处理文件上传逻辑
            cos.upload(false, COS_BIZ_TYPE.DOC_UPLOAD, files).then((result) => {
              console.log("ispublic...........result2", result, image_security);
              this.isPicUploading = false;
              const url = (result && result.url) || "";
              // 通过清除引用的方式重置input的值
              this.$refs.fileInput.value = "";
              try {
                cos
                  .getImagelUrl(COS_BIZ_TYPE.DOC_UPLOAD, result)
                  .then((res) => {
                    console.log("ispublic...........result...image", res);
                    this.$refs.htmlEditorRef.editor.restoreSelection();
                    this.$refs.htmlEditorRef.editor.dangerouslyInsertHtml(
                      `<img src="${res}" />`
                    );
                  })
                  .catch((err) => {
                    this.$message.error(
                      JSON.stringify(err) || "获取预览地址失败"
                    );
                    this.$refs.htmlEditorRef.lastSelection();
                    this.$refs.htmlEditorRef.editor.dangerouslyInsertHtml(
                      `<img src="${url}" />`
                    );
                  });
              } catch (error) {
                this.$refs.htmlEditorRef.lastSelection();
                this.$refs.htmlEditorRef.editor.dangerouslyInsertHtml(
                  `<img src="${url}" />`
                );
              }
            });
          }
        })
        .catch(() => {
          this.$message.error("图片长或宽不能超过2000，请重新选择");
          this.isPicUploading = false;
        });
    },
    // 发送问题
    onSendQuestion() {
      let { pattern, singleWorkflow } = this.$clientData.getPattern();
      let patternMessage = "";
      if (pattern === "single_workflow") {
        if (singleWorkflow.workflow_id && singleWorkflow.workflow_name) {
          if (!singleWorkflow.is_enable) {
            patternMessage = "您选择的工作流未启用，请开启后再调试";
          } else if (singleWorkflow.status === "DRAFT") {
            patternMessage = "您选择的工作流待调试，请先在工作流中对其进行调试";
          }
        } else {
          patternMessage = "您选择的工作流已被删除，请重新选择";
        }
      }
      if (patternMessage) {
        this.$alert({
          status: "warning",
          title: "",
          content: patternMessage,
          confirmText: "我知道了",
        });
      }

      this.checkAndUpdateWordLimit();

      if (this.wordCount > this.realWordLimit) {
        // this.$message.error('当前问题最长不超过500字符!');
        this.$message.error(`当前问题最长不超过${this.realWordLimit}字符!`);
        return;
      }
      if (this.docUrls.find((i) => i.resolveProcess === -1)) {
        this.$message.error("请删除解析失败的文件再发送！");
        return;
      }
      if (this.disableInput[this.currentVisitorId] || this.isSendDisabled)
        return;
      if (!this.question.trim() && this.docUrls.length === 0) {
        return;
      }
      const tempQuestion = html2Markdown(this.question);
      this.$emit("send", escapeUserInput(tempQuestion), this.docUrls);
      this.question = "";
      this.$refs.htmlEditorRef.editor.clear();
      this.docUrls = [];

      // 问题发出后，对话框立即滚动至底部
      this.$nextTick(() => {
        const sDom = document.querySelector(".client-chat");
        if (!sDom) return;
        scrollToBottom(sDom, sDom.scrollHeight);
      });
    },
    // 发送推荐问题
    onSendRecommendQuestion(val) {
      this.$emit("send", val);
    },
    // 结束会话
    onStopChat() {
      if (this.disableInput[this.currentVisitorId]) return;
      this.$eventHub.$emit("handleQuit");
      this.disableInput = {
        ...this.disableInput,
        [this.currentVisitorId]: true,
      };
      this.question = "";
    },
    // 停止生成
    onStopStream() {
      this.$clientData.stopGeneration();
      this.isGeneratingReply = false;
    },
    onAsrAnimationClick() {
      this.$emit("asrClick", {
        // 是否能进行录制
        canRecording: !(this.isGeneratingReply || this.isThinking === true),
      });
    },
    onAsrInput({ text }) {
      this.question = text;
    },
  },
  watch: {
    wordLimit(val) {
      this.realWordLimit = val;
    },
    question(val) {
      if (val.trim()) {
        this.isSendIconDisabled = false;
      } else {
        this.isSendIconDisabled = true;
      }
    },
  },
};
</script>

<style lang="less" scoped>
.question-input {
  //   position: fixed;
  // margin-top: -110px;
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(20px);
  border-bottom-left-radius: 12px;
  border-bottom-right-radius: 12px;
  .toolbar-info {
    display: inline-block;
    font-weight: 400;
    font-size: 12px;
    color: var(--color-text-caption);
    .red-txt {
      color: red;
    }
  }

  .stop-button {
    display: flex;
    justify-content: center;
    button {
      box-shadow: var(--shadow-small);
      border: none;
      padding: 6px 8px;
    }
  }

  .question-mask {
    width: 100%;
    height: 110px;
    position: absolute;
    z-index: 200;
    background: rgba(255, 255, 255, 0);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bolder;
  }

  .tips {
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: var(--text---c-color-text-caption, rgba(0, 11, 54, 0.41));
    font: var(--caption-regular);
    padding: 0 var(--spacing-quite-loose) var(--spacing-base-loose)
      var(--spacing-quite-loose);
  }

  &-inner {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    margin: var(--spacing-base-loose) var(--spacing-quite-loose);
    background-color: rgba(255, 255, 255, 0.8);
    border: var(--border-normal) solid;
    border-color: #e1e6f0;
    // box-shadow: 0px 24px 48px 0px rgba(0, 55, 159, 0.05);
    box-shadow: 0px 4px 12px 0px rgba(0, 55, 159, 0.05);

    border-radius: var(--radius-large);

    &.has-recommend {
      margin: 0 12px 12px;
    }

    &:hover {
      border: 1px solid var(--color-border-hover);
      box-shadow: 0px 4px 12px 0px rgba(0, 55, 159, 0.05);
    }

    .v-textarea--default {
      border-radius: var(--radius-large);
    }

    &:has(.question-input-inner),
    &:has(.v-textarea--focus),
    &.inputFocus {
      border-color: var(--color-primary-normal);
      border: 1px solid transparent;
      background-clip: padding-box, border-box;
      background-origin: padding-box, border-box;
      background-image: linear-gradient(to right, white, white),
        conic-gradient(from 180deg, #4a70ff, #53cbff 30%, #6054f5 70%, #4a70ff);
      box-shadow: 0px 4px 12px 0px rgba(0, 55, 159, 0.05);
      // .question-input-inner__textarea {
      //   padding: calc(var(--spacing-base) - 0.5px);
      //   padding-bottom: var(--spacing-base);
      // }
      // .question-input-inner__toolbar {
      //   padding: 7.5px;
      //   padding-top: 8px;
      // }
    }

    &.disabled {
      background-color: var(--color-fill-disable);
      border-color: var(--color-border-disable) !important;
      .v-textarea__placeholder {
        color: var(--color-text-disable);
      }
    }

    &__image-outer {
      display: flex;
      flex-flow: wrap;
      //   padding: var(--spacing-base-loose);
      padding: var(--spacing-base-loose) var(--spacing-base-loose) 0
        var(--spacing-base-loose);
      align-items: flex-start;
      gap: var(--spacing-base);
      align-self: stretch;
      border-top-left-radius: var(--radius-large);
      border-top-right-radius: var(--radius-large);
      //   background: var(--color-bg-contrast);
      .exist-img {
        cursor: pointer;
        margin-top: auto;
        margin-bottom: auto;
        box-sizing: border-box;
        display: flex;
        width: 54px;
        height: 54px;
        padding: 12px;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        gap: 12px;
        flex-shrink: 0;
        border-radius: var(--radius-large);
        border: var(--border-thickness) solid var(--color-divider-normal);
        position: relative;
        &:hover {
          .delete-button {
            visibility: visible;
          }
        }
        .delete-button {
          flex-shrink: 0;
          visibility: hidden;
          position: absolute;
          top: 2px;
          right: 2px;
          button {
            background: transparent;
            color: var(--color-text-caption);
            border: none;
            padding: 0px;
            height: 14px;
            display: flex;
          }
          .v-icon {
            width: 14px;
            height: 14px;
          }
        }
        & > img {
          object-fit: cover;
          height: 100%;
          width: 100%;
        }
      }
      .exist-doc {
        cursor: pointer;
        display: flex;
        width: 200px;
        padding: var(--spacing-base);
        align-items: center;
        gap: var(--spacing-tight);
        border-radius: var(--radius-large);
        background: var(--color-bg-1);
        /* shadow/--shadow-small-light */
        box-shadow: var(--shadow-small-light-x-1) var(--shadow-small-light-y-1)
            var(--shadow-large-light-blur-1) var(--shadow-small-light-spread-1)
            var(--shadow-small-light-color-1),
          var(--shadow-small-light-x-2) var(--shadow-small-light-y-2)
            var(--shadow-small-light-blur-2) var(--shadow-small-light-spread-2)
            var(--shadow-small-light-color-2),
          var(--shadow-small-light-x-3) var(--shadow-small-light-y-3)
            var(--shadow-small-light-blur-3) var(--shadow-small-light-spread-3)
            var(--shadow-small-light-color-3);
        position: relative;
        &:hover {
          .delete-button {
            visibility: visible;
          }
        }
        .delete-button {
          visibility: hidden;
          position: absolute;
          top: 5px;
          right: 0px;
          button {
            background: transparent;
            color: var(--color-text-caption);
            border: none;
          }
          .v-icon {
            width: 14px;
            height: 14px;
          }
        }
        .doc-icon-cont {
          display: flex;
          width: 36px;
          height: 36px;
          padding: 3px 4.5px;
          justify-content: center;
          align-items: center;
          flex-shrink: 0;
          border-radius: 1.5px;
          .doc-icon {
            width: 27px;
            height: 30px;
            flex-shrink: 0;
          }
        }
        .doc-txt-cont {
          .doc-txt-info {
            color: var(--color-text-caption);

            /* caption/--caption-regular */
            font-family: "PingFang SC";
            font-size: 12px;
            font-style: normal;
            font-weight: 400;
            line-height: 16px; /* 133.333% */
            &.red-txt {
              color: red;
            }
            .refresh-icon {
              width: 14px;
              height: 14px;
              position: absolute;
              margin-left: 3px;
            }
          }
          .filename {
            text-overflow: ellipsis;
            white-space: nowrap;
            overflow: hidden;
            color: var(--color-text-primary);
            width: 138px;
            /* body/--body-regular */
            font-family: "PingFang SC";
            font-size: 14px;
            font-style: normal;
            font-weight: 400;
            line-height: 20px; /* 142.857% */
          }
        }
      }
    }

    &__textarea {
      width: 100%;
      max-height: 40vh;
      overflow-y: auto;
      overflow-x: hidden;
      -webkit-overflow-scrolling: touch;
      border: none;
      background: none;
      &::-webkit-scrollbar {
        width: 5px;
        height: 8px;
      }
      /* 滚动条轨道 */
      &::-webkit-scrollbar-track {
        width: 0px;
      }

      /* 滚动条滑块 */
      &::-webkit-scrollbar-thumb {
        border-radius: var(--radius-large);
        background: rgba(17, 32, 70, 0.13);
      }

      .v-textarea__txt {
        overflow-y: overlay; /* 尝试使用 overlay */
        overflow-y: scroll; /* 如果 overlay 不支持，则使用 scroll */
        font-size: 14px;
        font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
          Helvetica Neue, Helvetica, Arial, PingFang SC, Microsoft YaHei,
          Noto Sans, sans-serif !important;
      }
    }

    .v-textarea--focus {
      border: none;
    }

    &__toolbar {
      display: flex;
      justify-content: space-between;
      z-index: 10;
      padding: 8px;

      .v-icon {
        cursor: pointer;
      }

      .toolbar-left {
        display: flex;
        gap: 4px;

        .toolbar-left-search-icon {
          display: flex;
          padding: 6px var(--spacing-base-loose);
          justify-content: center;
          align-items: center;
          gap: var(--spacing-tight);
          border-radius: var(--radius-round);
          /* body/--body-regular */
          // font-family: var(--font-family-normal);
          font-size: 14px;
          font-style: normal;
          font-weight: 400;
          line-height: 20px; /* 142.857% */
          cursor: pointer;
          &.is-active {
            background: var(--color-brand-0);
            color: var(--color-brand-5);
          }
          &.is-not-active {
            background: var(--color-gray-1);
            color: var(--color-text-secondary);
            // pointer-events: none;
          }
          &.display-none {
            display: none;
            pointer-events: none;
          }
        }

        .v-icon {
          &.disabled {
            cursor: not-allowed;
            color: var(--color-text-disable);
          }
        }

        .image-input {
          display: none;
        }

        .upload-label {
          box-sizing: border-box;
          display: flex;
          width: 32px;
          height: 32px;
          padding: 6px;
          justify-content: center;
          align-items: center;
          border-radius: 3px;
          &:hover {
            background: var(--fill---color-fill-hover, rgba(29, 57, 100, 0.05));
          }
          &:active {
            background: var(--fill---color-fill-active, #f0f6ff);
          }
        }
      }

      .toolbar-right {
        display: flex;
        gap: 16px;
        align-items: center;

        .stop-icon {
          display: flex;
          justify-content: center;
          align-items: center;
          cursor: pointer;
          padding: 6px;
        }
        .send-icon {
          display: flex;
          justify-content: center;
          align-items: center;
          height: 32px;
          width: 32px;
          cursor: pointer;
        }

        .send-icon {
          color: var(--color-primary-normal);
        }

        .stop-icon:hover,
        .send-icon:hover {
          background: var(--color-fill-hover);
          border-radius: 3px;
        }

        .stop-icon:active,
        .send-icon:active {
          background: var(--color-fill-active);
          border-radius: 3px;
        }

        .stop-icon.disabled,
        .send-icon.disabled {
          background: none;
          cursor: not-allowed;

          .v-icon {
            cursor: not-allowed;
            color: var(--color-text-disable);
          }
        }

        .split-line {
          align-self: center;
          width: 1px;
          height: 16px;
          background: var(--color-divider-normal);
          margin: 0 5px;
        }
      }
    }
  }

  /deep/.w-e-text-container {
    p {
      margin: 0;
      padding: 15px 0;
    }
  }
}

.asr-loading {
  background: var(--color-primary-light-normal);
}
</style>
